package org.smarthings.things.management.rest.impl;

import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import org.smarthings.things.commons.rest.VersionResource;
import org.smarthings.things.commons.utils.PaginationUtils;
import org.smarthings.things.commons.utils.QueryParametersUtils;
import org.smarthings.things.management.Thing;
import org.smarthings.things.management.ThingAction;
import org.smarthings.things.management.ThingProperty;
import org.smarthings.things.management.metadata.ThingActionArgumentMetadata;
import org.smarthings.things.management.metadata.ThingActionMetadata;
import org.smarthings.things.management.metadata.ThingPropertyMetadata;
import org.smarthings.things.management.metadata.provider.ThingActionMetadataProvider;
import org.smarthings.things.management.metadata.provider.ThingPropertyMetadataProvider;
import org.smarthings.things.management.rest.resources.ThingCollectionResource;
import org.smarthings.things.management.rest.resources.ThingEventSubscriptionResource;
import org.smarthings.things.management.rest.resources.ThingResource;
import org.smarthings.things.management.rest.resources.actions.ThingActionArgumentMetadataResource;
import org.smarthings.things.management.rest.resources.actions.ThingActionCollectionResource;
import org.smarthings.things.management.rest.resources.actions.ThingActionMetadataCollectionResource;
import org.smarthings.things.management.rest.resources.actions.ThingActionMetadataResource;
import org.smarthings.things.management.rest.resources.actions.ThingActionResource;
import org.smarthings.things.management.rest.resources.actions.ThingMetadataLocatorResource;
import org.smarthings.things.management.rest.resources.properties.ThingPropertyCollectionResource;
import org.smarthings.things.management.rest.resources.properties.ThingPropertyMetadataCollectionResource;
import org.smarthings.things.management.rest.resources.properties.ThingPropertyMetadataResource;
import org.smarthings.things.management.rest.resources.properties.ThingPropertyResource;

//TODO remove UriInfo parameter
public class ThingsResourceFactory {

	public static final String THINGS = "things";

	public static final String PROPERTIES = "properties";
	public static final String ACTIONS = "actions";
	public static final String EXECUTIONS = "executions";
	public static final String REGISTRATIONS = "registrations";
	public static final String SUBSCRIPTIONS = "subscriptions";
	public static final String METADATA = "metadata";
	public static final String VALUE = "value";
	public static final String ARGUMENTS = "arguments";

	public static final String THING_UUID = "{thingUUID}";
	public static final String PROPERTY_UUID = "{propertyUUID}";
	public static final String ACTION_UUID = "{actionUUID}";
	public static final String SUBSCRIPTION_UUID = "{subscriptionUUID}";

	public static final String PROPERTY_NAME = "{propertyName}";
	public static final String ACTION_NAME = "{actionName}";

	public static final String VERSION = "version";

	public static VersionResource getVersion(UriInfo uri) {
		VersionResource version = new VersionResource();
		version.setHref(UriBuilder.fromPath(THINGS).path(VERSION).build());
		version.setImplementationVersion("0.0.1");
		version.setImplementationVersion("0.0.1");
		return version;
	}

	public static ThingMetadataLocatorResource getThingMetadataLocator() {
		ThingMetadataLocatorResource resource = new ThingMetadataLocatorResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(METADATA).build());
		resource.setActions(UriBuilder.fromPath(THINGS).path(METADATA).path(ACTIONS).build());
		resource.setProperties(UriBuilder.fromPath(THINGS).path(METADATA).path(PROPERTIES).build());
		return resource;
	}

	public static ThingPropertyResource getThingProperty(Thing thing, ThingProperty property,
			UriInfo uri) {
		ThingPropertyResource resource = new ThingPropertyResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(THING_UUID).path(PROPERTIES)
				.path(PROPERTY_UUID).build(thing.getUUID(), property.getUUID()));
		resource.setUUID(property.getUUID());
		resource.setType(property.getType());
		// resource.setValue(DOMElementMarshaller.marshalThingPropertyValue(property)); FIXME
		if (property.getValue() != null) {
			resource.setValue(property.getValue().toString());
		}
		resource.setSubscribe(UriBuilder.fromPath(THINGS).path(THING_UUID).path(PROPERTIES)
				.path(PROPERTY_UUID).path(VALUE).build(thing.getUUID(), property.getUUID()));
		resource.setMetadata(UriBuilder.fromPath(THINGS).path(THING_UUID).path(METADATA)
				.path(PROPERTIES).path(PROPERTY_UUID).build(thing.getUUID(), property.getUUID()));
		return resource;

	}

	public static ThingPropertyCollectionResource getThingPropertyCollection(Thing thing,
			List<ThingProperty> properties, UriInfo uri) {
		ThingPropertyCollectionResource collection = new ThingPropertyCollectionResource();
		collection.setHref(UriBuilder.fromPath(THINGS).path(THING_UUID).path(PROPERTIES)
				.build(thing.getUUID()));
		if (properties != null && properties.size() > 0) {
			List<ThingPropertyResource> items = new ArrayList<ThingPropertyResource>(
					properties.size());
			Iterator<ThingProperty> iter = properties.iterator();
			while (iter.hasNext()) {
				ThingProperty property = (ThingProperty) iter.next();
				items.add(getThingProperty(thing, property, uri));
			}
			collection.setItems(items);
		}
		return collection;
	}

	public static ThingActionResource getThingAction(Thing thing, ThingAction action, UriInfo uri) {
		ThingActionResource resource = new ThingActionResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(THING_UUID).path(ACTIONS)
				.path(ACTION_UUID).build(thing.getUUID(), action.getUUID()));
		resource.setType(action.getType());
		resource.setUUID(action.getUUID());
		resource.setMetadata(UriBuilder.fromPath(THINGS).path(THING_UUID).path(METADATA)
				.path(ACTIONS).path(ACTION_UUID).build(thing.getUUID(), action.getUUID()));
		resource.setExecute(UriBuilder.fromPath(THINGS).path(THING_UUID).path(ACTIONS)
				.path(EXECUTIONS).path(ACTION_UUID).path(ARGUMENTS)
				.build(thing.getUUID(), action.getUUID()));
		return resource;

	}

	public static ThingActionCollectionResource getThingActionCollection(Thing thing,
			List<ThingAction> actions, UriInfo uri) {
		ThingActionCollectionResource collection = new ThingActionCollectionResource();
		collection.setHref(UriBuilder.fromPath(THINGS).path(THING_UUID).path(ACTIONS)
				.build(thing.getUUID()));
		if (actions != null && actions.size() > 0) {
			List<ThingActionResource> items = new ArrayList<ThingActionResource>(actions.size());
			Iterator<ThingAction> iter = actions.iterator();
			while (iter.hasNext()) {
				ThingAction action = (ThingAction) iter.next();
				items.add(getThingAction(thing, action, uri));

			}
			collection.setItems(items);
		}
		return collection;
	}

	public static ThingResource getThing(Thing thing, UriInfo uri) {
		ThingResource resource = new ThingResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(THING_UUID).build(thing.getUUID()));
		resource.setUnregister(UriBuilder.fromPath(THINGS).path(REGISTRATIONS).path(THING_UUID)
				.build(thing.getUUID()));

		resource.setUUID(thing.getUUID());
		resource.setName(thing.getName());
		resource.setTags(thing.getTags());
		resource.setType(thing.getType());
		resource.setState(thing.getState());

		if (thing.getProperties() != null) {
			resource.setProperties(UriBuilder.fromPath(THINGS).path(THING_UUID).path(PROPERTIES)
					.build(thing.getUUID()));
		}
		if (thing.getActions() != null) {
			resource.setActions(UriBuilder.fromPath(THINGS).path(THING_UUID).path(ACTIONS)
					.build(thing.getUUID()));
		}

		return resource;
	}

	public static ThingCollectionResource getThingCollection(List<Thing> things, int offset,
			int limit, UriInfo uri) {
		ThingCollectionResource collection = new ThingCollectionResource();
		collection.setHref(UriBuilder.fromPath(THINGS).build());
		collection.setOffset(offset);
		collection.setLimit(limit);
		int size = things.size();
		collection.setFirst(UriBuilder.fromPath(THINGS)
				.queryParam(QueryParametersUtils.LIMIT, limit)
				.queryParam(QueryParametersUtils.OFFSET, 0).build());
		collection.setLast(UriBuilder
				.fromPath(THINGS)
				.queryParam(QueryParametersUtils.LIMIT, limit)
				.queryParam(QueryParametersUtils.OFFSET,
						size - PaginationUtils.calculateLastPageSize(offset, limit, size)).build());
		int next = PaginationUtils.calculateNextPageOffset(offset, limit, size);
		if (next < size) {
			collection.setNext(UriBuilder.fromPath(THINGS)
					.queryParam(QueryParametersUtils.LIMIT, limit)
					.queryParam(QueryParametersUtils.OFFSET, next).build());
		}
		int previous = PaginationUtils.calculatePreviousPageOffset(offset, limit, size);
		if (previous > 0) {
			collection.setPrevious(UriBuilder.fromPath(THINGS)
					.queryParam(QueryParametersUtils.LIMIT, limit)
					.queryParam(QueryParametersUtils.OFFSET, previous).build());
		}

		if (things != null && things.size() > 0) {
			List<ThingResource> items = new ArrayList<ThingResource>(things.size());
			Iterator<Thing> thingsIterator = things.iterator();
			while (thingsIterator.hasNext()) {
				ThingResource resource = getThing(thingsIterator.next(), uri);
				items.add(resource);
			}
			collection.setItems(items);
		}
		return collection;
	}

	public static ThingPropertyMetadataResource getThingPropertyMetadata(Thing thing,
			ThingProperty property, UriInfo uri) {
		ThingPropertyMetadataResource resource = new ThingPropertyMetadataResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(THING_UUID).path(METADATA)
				.path(PROPERTIES).path(PROPERTY_UUID).build(thing.getUUID(), property.getUUID()));
		// resource.setMetadata(DOMElementMarshaller.marshalThingPropertyMetaData(property
		// .getMetadata()));
		ThingPropertyMetadata metadata = property.getMetadata();
		resource.setDesctiption(metadata.getDesctiption());
		resource.setType(metadata.getType());
		resource.setUnit(metadata.getUnit());
		resource.setMax(metadata.getMax());
		resource.setMin(metadata.getMin());
		resource.setAccess(metadata.getAccess());
		return resource;
	}

	public static ThingPropertyMetadataResource getThingPropertyMetadata(String propertyName,
			ThingPropertyMetadataProvider provider) {
		ThingPropertyMetadata propertyMetadata = provider.getThingPropertyDescription(propertyName);
		ThingPropertyMetadataResource resource = new ThingPropertyMetadataResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(METADATA).path(PROPERTIES)
				.path(PROPERTY_NAME).build(propertyName));
		// resource.setMetadata(DOMElementMarshaller.marshalThingPropertyMetaData(propertyMetadata));
		resource.setDesctiption(propertyMetadata.getDesctiption());
		resource.setType(propertyMetadata.getType());
		resource.setUnit(propertyMetadata.getUnit());
		resource.setMin(propertyMetadata.getMin());
		resource.setMax(propertyMetadata.getMax());
		resource.setAccess(propertyMetadata.getAccess());
		return resource;

	}

	public static ThingPropertyMetadataCollectionResource getThingPropertyMetadataCollection(
			ThingPropertyMetadataProvider provider) {
		ThingPropertyMetadataCollectionResource collection = new ThingPropertyMetadataCollectionResource();
		collection.setHref(UriBuilder.fromPath(THINGS).path(METADATA).path(PROPERTIES).build());
		List<String> properties = provider.getThingProperties();
		List<ThingPropertyMetadataResource> items = new ArrayList<ThingPropertyMetadataResource>(
				properties.size());
		if (properties != null && properties.size() > 0) {
			for (String property : properties) {
				ThingPropertyMetadataResource resource = getThingPropertyMetadata(property,
						provider);
				items.add(resource);
			}
			collection.setItems(items);
		}
		return collection;
	}

	public static ThingActionMetadataResource getThingActionMetadata(Thing thing,
			ThingAction action, UriInfo uri) {
		ThingActionMetadata metadata = action.getMetadata();
		ThingActionMetadataResource resource = new ThingActionMetadataResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(THING_UUID).path(METADATA).path(ACTIONS)
				.path(ACTION_UUID).build(thing.getUUID(), action.getUUID()));
		resource.setDescription(metadata.getDesctiption());

		List<ThingActionArgumentMetadata> argumentsMetadata = metadata.getArguments();
		if (argumentsMetadata != null && argumentsMetadata.size() > 0) {
			List<ThingActionArgumentMetadataResource> arguments = new ArrayList<ThingActionArgumentMetadataResource>(
					argumentsMetadata.size());
			Iterator<ThingActionArgumentMetadata> iter = argumentsMetadata.iterator();
			while (iter.hasNext()) {
				ThingActionArgumentMetadata argumentMetadata = iter.next();
				ThingActionArgumentMetadataResource argument = new ThingActionArgumentMetadataResource();
				// argument.setMetadata(DOMElementMarshaller
				// .marshalThingActionArgumentMetaData(argumentMetadata));
				argument.setAlias(argumentMetadata.getAlias());
				argument.setDescription(argumentMetadata.getDescription());
				argument.setResolution(argumentMetadata.getResolution());
				argument.setType(argumentMetadata.getType());
				argument.setMax(argumentMetadata.getMax());
				argument.setMin(argumentMetadata.getMin());
				arguments.add(argument);
			}
			resource.setArguments(arguments);
		}
		return resource;
	}

	public static ThingActionMetadataResource getThingActionMetadata(String actionName,
			ThingActionMetadataProvider provider) {
		ThingActionMetadata actionMetadata = provider.getThingActionDescription(actionName);
		ThingActionMetadataResource resource = new ThingActionMetadataResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(METADATA).path(ACTIONS).path(ACTION_NAME)
				.build(actionName));
		resource.setDescription(actionMetadata.getDesctiption());

		List<ThingActionArgumentMetadata> argumentsMetadata = actionMetadata.getArguments();
		if (argumentsMetadata != null && argumentsMetadata.size() > 0) {
			List<ThingActionArgumentMetadataResource> arguments = new ArrayList<ThingActionArgumentMetadataResource>(
					argumentsMetadata.size());
			Iterator<ThingActionArgumentMetadata> iter = argumentsMetadata.iterator();
			while (iter.hasNext()) {
				ThingActionArgumentMetadata argumentMetadata = iter.next();
				ThingActionArgumentMetadataResource argument = new ThingActionArgumentMetadataResource();
				// argument.setMetadata(DOMElementMarshaller
				// .marshalThingActionArgumentMetaData(argumentMetadata));
				argument.setAlias(argumentMetadata.getAlias());
				argument.setDescription(argumentMetadata.getDescription());
				argument.setResolution(argumentMetadata.getResolution());
				argument.setType(argumentMetadata.getType());
				argument.setMax(argumentMetadata.getMax());
				argument.setMin(argumentMetadata.getMin());
				arguments.add(argument);
			}
			resource.setArguments(arguments);
		}
		return resource;
	}

	public static ThingActionMetadataCollectionResource getThingActionMetadataCollection(
			ThingActionMetadataProvider provider) {
		ThingActionMetadataCollectionResource collection = new ThingActionMetadataCollectionResource();
		collection.setHref(UriBuilder.fromPath(THINGS).path(METADATA).path(ACTIONS).build());
		List<String> actions = provider.getThingActions();
		List<ThingActionMetadataResource> items = new ArrayList<ThingActionMetadataResource>(
				actions.size());
		if (actions != null && actions.size() > 0) {
			for (String action : actions) {
				ThingActionMetadataResource resource = getThingActionMetadata(action, provider);
				items.add(resource);
			}
			collection.setItems(items);
		}
		return collection;
	}

	public static ThingEventSubscriptionResource getThingEventSubscription(String subscriptionUUID,
			String filter, URI callback, UriInfo uri) {
		ThingEventSubscriptionResource resource = new ThingEventSubscriptionResource();
		resource.setHref(UriBuilder.fromPath(THINGS).path(SUBSCRIPTIONS).path(SUBSCRIPTION_UUID)
				.build(subscriptionUUID));
		resource.setCallback(callback);
		resource.setFilter(filter);

		return resource;
	}
}
